/*** Created Time: 2022年7月4日 20:42:55  ***/
/*** Author: Eric                        ***/

#include <onix/arena.h>
#include <onix/memory.h>
#include <onix/string.h>
#include <onix/stdlib.h>
#include <onix/assert.h>

/* 内核块内存管理 */

extern u32 free_pages;
static arena_descriptor_t descriptors[DESC_COUNT];

// arena 初始化
void arena_init(void)
{
    u32 block_size = 16;    // 最小块大小为 16 字节
                            // 最大块大小为 1024 字节
    for (size_t i = 0; i < DESC_COUNT; i++)
    {
        arena_descriptor_t *desc = &descriptors[i];
        desc->block_size = block_size;
        desc->total_block = (PAGE_SIZE - sizeof(arena_t)) / block_size;
        list_init(&desc->free_list);
        block_size <<= 1;   // block *= 2;
    }
}

// 获得 arena 第 idx 块内存指针
static void *get_arena_block(arena_t *arena, u32 idx)
{
    assert(arena->desc->total_block > idx);
    void *addr = (void *)(arena + 1);
    u32 gap = idx * arena->desc->block_size;
    return addr + gap;
}

// 得到 arena 起始地址
static arena_t *get_block_arena(block_t *block)
{
    return (arena_t *)((u32)block & 0xfffff000);
}

void *kmalloc(size_t size)
{
    arena_descriptor_t *desc = NULL;
    arena_t *arena;
    block_t *block;
    char *addr;

    if (size > 1024) {
        u32 asize = size + sizeof(arena_t);         // 总共需要的内存大小(字节)
        u32 count = div_round_up(asize, PAGE_SIZE); // 计算需要多少页才能放下所需内存

        arena = (arena_t *)alloc_kpage(count);
        memset(arena, 0, count * PAGE_SIZE);
        arena->large = true;    // 表示超过 1024 字节
        arena->count = count;   // 表示页数
        arena->desc = NULL;
        arena->magic = ONIX_MAGIC;

        addr = (char *)((u32)arena + sizeof(arena_t));
        return addr;
    }

    // 小于等于 1024 字节时：
    for (size_t i = 0; i < DESC_COUNT; i++) {
        // 一定可以找到一个内存描述符，满足 size 的大小
        desc = &descriptors[i];
        if (desc->block_size >= size)
            break;
    }

    assert(desc != NULL);

    if (list_empty(&desc->free_list)) {
        // arena 为页开始的位置
        arena = (arena_t *)alloc_kpage(1);
        memset(arena, 0, PAGE_SIZE);

        arena->desc = desc;
        arena->large = false;               // 未超过 1024 字节
        arena->count = desc->total_block;   // 剩余块数量
        arena->magic = ONIX_MAGIC;

        for (size_t i = 0; i < desc->total_block; i++) {
            block = get_arena_block(arena, i);
            assert(!list_search(&arena->desc->free_list, block));
            list_push(&arena->desc->free_list, block);  // 插入链表
            assert(list_search(&arena->desc->free_list, block));
        }
    }

    // 需要假设会调用 kfree, 将该内存块重新链接进链表中
    // 否则该内存块则永远失去了与内核的管理
    block = list_pop(&desc->free_list);

    arena = get_block_arena(block);
    assert(arena->magic == ONIX_MAGIC && !arena->large);

    // memset(block, 0, desc->block_size);

    // arena 空闲内存块数量
    arena->count--;

    return block;
}

void kfree(void *ptr)
{
    // 默认前提 ptr 是由 kmalloc 得到的
    assert(ptr);

    block_t *block = (block_t *)ptr;
    arena_t *arena = get_block_arena(block);    // 得到由内存块对应的 arena 地址

    assert(arena->large == 1 || arena->large == 0);
    assert(arena->magic == ONIX_MAGIC);

    // 当大于 1024 字节内存时，直接调用 free_kpage
    if (arena->large) {
        free_kpage((u32)arena, arena->count);
        return;
    }

    // 重新将该 block 链接进内存池中
    // 因此是基于 kmalloc kfree 成对使用的前提
    list_push(&arena->desc->free_list, block);
    arena->count++;

    // 如果一页内存的所有内存块都被释放了，则将该页内存释放
    if (arena->count == arena->desc->total_block) {
        for (size_t i = 0; i < arena->desc->total_block; i++) {
            block = get_arena_block(arena, i);
            assert(list_search(&arena->desc->free_list, block));
            list_remove(block);
            assert(!list_search(&arena->desc->free_list, block));
        }
        free_kpage((u32)arena, 1);
    }
}
